/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "hks_secure_access.h"

#include "hks_log.h"
#include "hks_param.h"
#include "hks_type_inner.h"
#include "hks_mem.h"
#include "hks_keyblob.h"
#include "hks_keynode.h"
#include "hks_crypto_hal.h"
#include "hks_useridm_api_wrap.h"
#include "securec.h"

#include "hks_core_hal_api.h"

#define BYTES_PER_POS 8
#define S_TO_MS 1000

static int32_t CheckInitParamSetValidityAndGet(const struct HksParamSet *keyBlobParamSet,
    const struct HksParamSet *initParamSet, bool *isNeedSecureSuccess, uint32_t *challengePos)
{
    struct HksParam *blobUserAuthType = NULL;
    int32_t ret = HksGetParam(keyBlobParamSet, HKS_TAG_USER_AUTH_TYPE, &blobUserAuthType);
    if (ret == HKS_ERROR_PARAM_NOT_EXIST) {
        *isNeedSecureSuccess = false;
        return HKS_SUCCESS;
    }

    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get blob user auth type failed!");
        return ret;
    }

    struct HksParam *blobChallengeType = NULL;
    ret = HksGetParam(keyBlobParamSet, HKS_TAG_CHALLENGE_TYPE, &blobChallengeType);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get blob challenge type failed!");
        return ret;
    }

    struct HksParam *challengePosParam = NULL;
    if (blobChallengeType->uint32Param == HKS_CHALLENGE_TYPE_CUSTOM) {
        ret = HksGetParam(initParamSet, HKS_TAG_CHALLENGE_POS, &challengePosParam);
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("get init paramSet's challenge pos failed!");
            return ret;
        }
        if (challengePosParam->uint32Param > HKS_CHALLENGE_POS_3) {
            HKS_LOG_E("challenge position should in range of 0~3!");
            return HKS_ERROR_INVALID_ARGUMENT;
        }
        *challengePos = challengePosParam->uint32Param;
    } else {
        *challengePos = 0;
    }

    *isNeedSecureSuccess = true;
    return HKS_SUCCESS;
}

static int32_t InitChallengeParams(struct HksParamSet *paramSet, struct HksBlob *challengeBlob)
{
    int32_t ret = HksCryptoHalFillRandom(challengeBlob);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("generate challenge failed!");
        return ret;
    }

    struct HksParam challengeParam;
    challengeParam.tag = HKS_TAG_KEY_INIT_CHALLENGE;
    challengeParam.blob = *challengeBlob;
    ret = HksAddParams(paramSet, &challengeParam, 1);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("add challenge params fail");
        return ret;
    }
    return HKS_SUCCESS;
}

static int32_t InitAccessTimeParams(struct HksParamSet *paramSet)
{
    uint64_t curTime = 0;
    int32_t ret = HksCoreHalElapsedRealTime(&curTime);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get elapsed real time failed!");
        return ret;
    }

    struct HksParam accessTimeParam;
    accessTimeParam.tag = HKS_TAG_ACCESS_TIME;
    accessTimeParam.uint32Param = curTime / S_TO_MS;
    ret = HksAddParams(paramSet, &accessTimeParam, 1);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("add access time param fail");
        return ret;
    }
    return HKS_SUCCESS;
}

static int32_t BuildAuthRuntimeParamSet(bool isSecureAccess, uint32_t challengePos, struct HksBlob *token,
    struct HksParamSet **outParamSet)
{
    struct HksParamSet *paramSet = NULL;
    int32_t ret = HksInitParamSet(&paramSet);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("init keyNode auth runtime param set fail");
        return ret;
    }

    do {
        struct HksParam defineParams[] = {
            { .tag = HKS_TAG_IS_SECURE_ACCESS, .boolParam = isSecureAccess },
            { .tag = HKS_TAG_KEY_AUTH_RESULT, .int32Param = HKS_AUTH_RESULT_INIT },
            { .tag = HKS_TAG_CHALLENGE_POS, .uint32Param = challengePos }
        };

        ret = HksAddParams(paramSet, defineParams, sizeof(defineParams) / sizeof(defineParams[0]));
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("add runtime defineParams fail");
            break;
        }

        ret = InitAccessTimeParams(paramSet);
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("init access time params failed!");
            break;
        }

        uint8_t challenge[TOKEN_SIZE] = {0};
        struct HksBlob challengeBlob = {TOKEN_SIZE, challenge};
        ret = InitChallengeParams(paramSet, &challengeBlob);
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("init challenge params failed!");
            break;
        }

        ret = HksBuildParamSet(&paramSet);
        if (ret != HKS_SUCCESS) {
            break;
        }

        if (token->size < TOKEN_SIZE) {
            ret = HKS_ERROR_BUFFER_TOO_SMALL;
            break;
        }

        if (memcpy_s(token->data, token->size, challenge, TOKEN_SIZE) != EOK) {
            HKS_LOG_E("copy token failed");
            ret = HKS_ERROR_BAD_STATE;
            break;
        }

        *outParamSet = paramSet;
        return HKS_SUCCESS;
    } while(0);

    HksFreeParamSet(&paramSet);
    return ret;
}

static int32_t HksVerifyKeyChallenge(struct HuksKeyNode *keyNode, struct HksUserAuthToken *token, uint32_t challengePos,
    uint32_t checkLen)
{
    struct HksParam *challenge = NULL;
    int32_t ret = HksGetParam(keyNode->authRuntimeParamSet, HKS_TAG_KEY_INIT_CHALLENGE, &challenge);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get init challenge failed!");
        return HKS_ERROR_BAD_STATE;
    }
    if (checkLen + challengePos * BYTES_PER_POS > challenge->blob.size) {
        HKS_LOG_E("check challenge too long!");
        return HKS_ERROR_INVALID_ARGUMENT;
    }
    if (HksMemCmp(challenge->blob.data + challengePos * BYTES_PER_POS, token->challenge + challengePos * BYTES_PER_POS, checkLen) != 0) {
        HKS_LOG_E("verify challenge failed!");
        return HKS_ERROR_KEY_AUTH_FAILED;
    }
    return HKS_SUCCESS;
}

static int32_t HksVerifyKeyTimestamp(struct HuksKeyNode *keyNode, struct HksUserAuthToken *token)
{
    struct HksParam *timeOut = NULL;
    int32_t ret = HksGetParam(keyNode->keyBlobParamSet, HKS_TAG_AUTH_TIMEOUT, &timeOut);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get time out failed!");
        return HKS_ERROR_BAD_STATE;
    }

    struct HksParam *accessTime = NULL;
    ret = HksGetParam(keyNode->authRuntimeParamSet, HKS_TAG_ACCESS_TIME, &accessTime);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get access time failed!");
        return HKS_ERROR_BAD_STATE;
    }
    
    // ms to s
    uint32_t authTokenTime = token->time / 1000;
    if (accessTime->uint32Param - authTokenTime > timeOut->uint32Param || 
        authTokenTime - accessTime->uint32Param > timeOut->uint32Param) {
        HKS_LOG_E("auth token time out!");
        return HKS_ERROR_KEY_AUTH_FAILED;
    }
    return HKS_SUCCESS;
}

static int32_t CheckAuthToken(const struct HksBlob *authTokenParam)
{
    if (authTokenParam->size != sizeof(struct HksUserAuthToken)) {
        HKS_LOG_E("size of authTokenParam not match HksUserAuthToken!");
        return HKS_ERROR_INVALID_ARGUMENT;
    }
    return HKS_SUCCESS;
}

static int32_t ParseAuthToken(const struct HksBlob *inAuthTokenParam, struct HksUserAuthToken **outAuthToken)
{
    int32_t ret = CheckAuthToken(inAuthTokenParam);
    if (ret != HKS_SUCCESS) {
        return ret;
    }

    struct HksUserAuthToken *authToken = NULL;
    do {
        authToken = (struct HksUserAuthToken *)HksMalloc(sizeof(struct HksUserAuthToken));
        if (authToken == NULL) {
            HKS_LOG_E("malloc for authToken failed!");
            ret = HKS_ERROR_MALLOC_FAIL;
            break;
        }

        if (memcpy_s(authToken, sizeof(struct HksUserAuthToken), inAuthTokenParam->data,
            inAuthTokenParam->size) != EOK) {
            HKS_LOG_E("memcpy for authToken failed!");
            ret = HKS_ERROR_BAD_STATE;
            break;
        }

        *outAuthToken = authToken;
        return HKS_SUCCESS;
    } while (0);

    HKS_FREE_PTR(authToken);
    return ret;
}

static int32_t GetAuthToken(const struct HksParamSet *paramSet, struct HksUserAuthToken **authToken)
{
    struct HksParam *authTokenParam = NULL;
    int32_t ret = HksGetParam(paramSet, HKS_TAG_AUTH_TOKEN, &authTokenParam);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get auth token param failed!");
        return HKS_ERROR_INVALID_ARGUMENT;
    }

    ret = ParseAuthToken(&authTokenParam->blob, authToken);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("parse auth token failed!");
        return HKS_ERROR_INVALID_ARGUMENT;
    }
    return HKS_SUCCESS;
}

static int32_t GetChallengePos(const struct HksParamSet *paramSet, uint32_t *pos)
{
    int32_t ret;
    struct HksParam *posParam = NULL;
    ret = HksGetParam(paramSet, HKS_TAG_CHALLENGE_POS, &posParam);
    if (ret != HKS_SUCCESS || posParam == NULL) {
        HKS_LOG_E("get challenge pos failed!");
        return HKS_ERROR_INVALID_ARGUMENT;
    }
    *pos = posParam->uint32Param;
    return ret;
}

static int32_t GetChallengeType(const struct HksParamSet *paramSet, uint32_t *type)
{
    int32_t ret;
    struct HksParam *typeParam = NULL;
    ret = HksGetParam(paramSet, HKS_TAG_CHALLENGE_TYPE, &typeParam);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get challenge pos failed!");
        return HKS_ERROR_BAD_STATE;
    }
    *type = typeParam->uint32Param;
    return HKS_SUCCESS;
}

static int32_t VerifyCustomChallenge(struct HuksKeyNode *keyNode, struct HksUserAuthToken *authToken)
{
    uint32_t pos = 0;
    int32_t ret = GetChallengePos(keyNode->authRuntimeParamSet, &pos);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get challenge pos failed!");
        return HKS_ERROR_INVALID_ARGUMENT;
    }

    return HksVerifyKeyChallenge(keyNode, authToken, pos, BYTES_PER_POS);
}

static int32_t VerifyNormalChallenge(struct HuksKeyNode *keyNode, struct HksUserAuthToken *authToken)
{
    return HksVerifyKeyChallenge(keyNode, authToken, 0, TOKEN_SIZE);
}

static int32_t VerifyChallengeOrTimeStamp(struct HuksKeyNode *keyNode, struct HksUserAuthToken *authToken)
{
    uint32_t blobChallengeType;
    int32_t ret = GetChallengeType(keyNode->keyBlobParamSet, &blobChallengeType);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get challenge type failed!");
        return ret;
    }

    switch (blobChallengeType) {
        case HKS_CHALLENGE_TYPE_NORMAL:
            ret = VerifyNormalChallenge(keyNode, authToken);
            break;
        case HKS_CHALLENGE_TYPE_CUSTOM:
            ret = VerifyCustomChallenge(keyNode, authToken);
            break;
        case HKS_CHALLENGE_TYPE_NONE:
            ret = HksVerifyKeyTimestamp(keyNode, authToken);
            break;
        default:
            ret = HKS_ERROR_BAD_STATE;
    }
    return ret;
}

static int32_t VerifySecUidIfNeed(const struct HksParamSet *keyBlobParamSet, const struct HksUserAuthToken *authToken,
    uint32_t userAuthType, uint32_t authAccessType)
{
    if ((userAuthType & HKS_USER_AUTH_TYPE_PIN) == 0 ||
        (authAccessType & HKS_AUTH_ACCESS_INVALID_CLEAR_PASSWORD) == 0) {
        return HKS_SUCCESS;
    }

    struct HksParam *secUid = NULL;
    int32_t ret = HksGetParam(keyBlobParamSet, HKS_TAG_USER_AUTH_SECURE_UID, &secUid);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get sec uid failed!");
        return HKS_ERROR_BAD_STATE;
    }

    if (secUid->blob.size != sizeof(uint64_t)) {
        HKS_LOG_E("invalid sec uid param!");
        return HKS_ERROR_BAD_STATE;
    }

    if (HksMemCmp(secUid->blob.data, &authToken->secureUid, sizeof(uint64_t)) != 0) {
        HKS_LOG_E("check sec uid failed!");
        return HKS_ERROR_KEY_AUTH_FAILED;
    }
    return HKS_SUCCESS;
}

static int32_t VerifyEnrolledIdInfoIfNeed(const struct HksParamSet *keyBlobParamSet,
    const struct HksUserAuthToken *authToken, uint32_t blobAuthType, uint32_t authAccessType)
{
    if ((blobAuthType & (HKS_USER_AUTH_TYPE_FACE | HKS_USER_AUTH_TYPE_FINGERPRINT)) == 0 ||
        (authAccessType & HKS_AUTH_ACCESS_INVALID_NEW_BIO_ENROLL) == 0) {
        return HKS_SUCCESS;
    }

    uint32_t authTokenAuthType = 0;
    int32_t ret = HksConvertUserIamTypeToHksType(HKS_AUTH_TYPE, authToken->authType, &authTokenAuthType);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("invalid user iam auth type:not support!");
        return HKS_ERROR_NOT_SUPPORTED;
    }

    if ((authTokenAuthType & blobAuthType) == 0) {
        HKS_LOG_E("current keyblob auth do not support current auth token auth type!");
        return HKS_ERROR_KEY_AUTH_FAILED;
    }

    struct HksParam *enrolledIdInfo = NULL;
    ret = HksGetParam(keyBlobParamSet, HKS_TAG_USER_AUTH_ENROLL_ID_INFO, &enrolledIdInfo);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get enrolled info param failed!");
        return HKS_ERROR_BAD_STATE;
    }

    struct HksBlob enrolledIdInfoBlob = enrolledIdInfo->blob;
    if (enrolledIdInfoBlob.size < ENROLLED_ID_INFO_MIN_LEN) {
        HKS_LOG_E("get enrolled info param invalid!");
        return HKS_ERROR_BAD_STATE;
    }

    uint32_t enrolledIdNum = 0;
    if (memcpy_s(&enrolledIdNum, sizeof(uint32_t), enrolledIdInfoBlob.data, sizeof(uint32_t)) != EOK) {
        HKS_LOG_E("copy enrolled id len failed!");
        return HKS_ERROR_BAD_STATE;
    }
    uint32_t index = sizeof(uint32_t);

    for (uint32_t i = 0; i < enrolledIdNum && index < enrolledIdInfoBlob.size; ++i) {
        uint32_t authType = 0;
        if (memcpy_s(&authType, sizeof(uint32_t), enrolledIdInfoBlob.data + index, sizeof(uint32_t)) != EOK) {
            HKS_LOG_E("copy authType failed!");
            return HKS_ERROR_BAD_STATE;
        }
        index += sizeof(uint32_t);

        uint64_t enrolledId = 0;
        if (memcpy_s(&enrolledId, sizeof(uint64_t), enrolledIdInfoBlob.data + index, sizeof(uint64_t)) != EOK) {
            HKS_LOG_E("copy enrolledId failed!");
            return HKS_ERROR_BAD_STATE;
        }
        index += sizeof(uint64_t);

        if (authType == authToken->authType) {
            if (enrolledId == authToken->enrolledId) {
                return HKS_SUCCESS;
            }
            return HKS_ERROR_KEY_AUTH_FAILED;
        }
    }
    HKS_LOG_E("match enrolled id failed!");
    return HKS_ERROR_KEY_AUTH_FAILED;
}

static int32_t VerifyAuthTokenInfo(struct HuksKeyNode *keyNode, struct HksUserAuthToken *authToken)
{
    struct HksParamSet *keyBlobParamSet = keyNode->keyBlobParamSet;
    struct HksParam *userAuthType = NULL;
    int32_t ret = HksGetParam(keyBlobParamSet, HKS_TAG_USER_AUTH_TYPE, &userAuthType);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get userAuthType type failed!");
        return HKS_ERROR_BAD_STATE;
    }

    struct HksParam *authAccessType = NULL;
    ret = HksGetParam(keyBlobParamSet, HKS_TAG_KEY_AUTH_ACCESS_TYPE, &authAccessType);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get auth access type failed!");
        return HKS_ERROR_BAD_STATE;
    }

    ret = VerifySecUidIfNeed(keyBlobParamSet, authToken, userAuthType->uint32Param, authAccessType->uint32Param);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("verify sec uid failed!");
        return ret;
    }

    ret = VerifyEnrolledIdInfoIfNeed(keyBlobParamSet, authToken, userAuthType->uint32Param,
        authAccessType->uint32Param);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("verify enrolled id info failed!");
        return ret;
    }

    return ret;
}

int32_t HksCoreSecureAccessInitParams(struct HuksKeyNode *keyNode, const struct HksParamSet *initParamSet,
    struct HksBlob *token)
{
        HKS_LOG_E("HksCoreSecureAccessInitParams start ");

    if (keyNode == NULL || initParamSet == NULL || token == NULL) {
        HKS_LOG_E("the pointer param is invalid");
        return HKS_ERROR_NULL_POINTER;
    }

    bool isNeedSecureAccessControl = false;
    uint32_t challengePos = 0;
    int32_t ret = CheckInitParamSetValidityAndGet(keyNode->keyBlobParamSet, initParamSet, &isNeedSecureAccessControl,
        &challengePos);

    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("check init params failed");
        return ret;
    }

    struct HksParamSet *authRuntimeParamSet = NULL;
    ret = BuildAuthRuntimeParamSet(isNeedSecureAccessControl, challengePos, token, &authRuntimeParamSet);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("check init params failed");
        return ret;
    }

    keyNode->authRuntimeParamSet = authRuntimeParamSet;

    HKS_LOG_E("HksCoreSecureAccessInitParams done");
    return HKS_SUCCESS;
}


int32_t HksCoreSecureAccessVerifyParams(struct HuksKeyNode *keyNode, const struct HksParamSet *paramSet)
{
    HKS_LOG_E("HksCoreSecureAccessVerifyParams start ");

    if (keyNode == NULL || paramSet == NULL) {
        HKS_LOG_E("the pointer param is invalid");
        return HKS_ERROR_NULL_POINTER;
    }

    struct HksParam *isSecureAccess = NULL;
    int32_t ret = HksGetParam(keyNode->authRuntimeParamSet, HKS_TAG_IS_SECURE_ACCESS, &isSecureAccess);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get isSecureAccess failed!");
        return HKS_ERROR_BAD_STATE;
    }

    if (!isSecureAccess->boolParam) {
        return HKS_SUCCESS;
    }

    struct HksParam *authResult = NULL;
    ret = HksGetParam(keyNode->authRuntimeParamSet, HKS_TAG_KEY_AUTH_RESULT, &authResult);
    if (ret != HKS_SUCCESS) {
        HKS_LOG_E("get authResult failed!");
        return HKS_ERROR_BAD_STATE;
    }

    if (authResult->uint32Param == HKS_AUTH_RESULT_SUCCESS) {
        return HKS_SUCCESS;
    }

    if (authResult->uint32Param == HKS_AUTH_RESULT_FAILED) {
        return HKS_ERROR_KEY_AUTH_FAILED;
    }

    struct HksUserAuthToken *authToken = NULL;
    do {
        ret = GetAuthToken(paramSet, &authToken);

        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("get auth token failed!");
            break;
        }

        ret = HksVerifyAuthTokenSign(authToken);
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("verify the auth token sign failed!");
            break;
        }

        ret = VerifyChallengeOrTimeStamp(keyNode, authToken);
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("verify challenge failed!");
            break;
        }

        ret = VerifyAuthTokenInfo(keyNode, authToken);
        if (ret != HKS_SUCCESS) {
            HKS_LOG_E("verify auth token info failed!");
            break;
        }
    } while(0);

    if (ret == HKS_SUCCESS) {
        authResult->uint32Param = HKS_AUTH_RESULT_SUCCESS;
    } else {
        authResult->uint32Param = HKS_AUTH_RESULT_FAILED;
    }

    HKS_LOG_E("HksCoreSecureAccessVerifyParams done ");

    HKS_FREE_PTR(authToken);
    return ret;
}